在前一篇(Day 28)我們整合了 RBAC + ABAC,今天要更進一步,將 Scope(範圍控制) 也引入授權邏輯,並討論一些專案中的最佳實務做法。
今天的案例同時考慮三個層面:
ROLE_ADMIN。FINANCE。read 或 write。這三者加在一起,就能實現更精細的授權策略。
// RBAC: 只有 ROLE_ADMIN 可進
@GetMapping("/admin")
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
public String adminOnly() {
    return "這是管理員專區 (RBAC)";
}
// ABAC + Scope + Role: 三重檢查
@GetMapping("/finance/report")
@PreAuthorize("hasAuthority('ROLE_ADMIN') and @scopeSecurity.hasScope(authentication, 'read') and @departmentSecurity.checkDepartment(authentication, 'FINANCE')")
public String financeReport() {
    return "這是財務報表,只有 Finance 部門 + Admin + 有 read scope 才能看 (Scope+Role+Attribute)";
}
/admin → 單純 RBAC。/finance/report → 必須同時符合 角色 + Scope + 部門。String role = claims.get("role", String.class);
String scope = claims.get("scope", String.class);
UsernamePasswordAuthenticationToken authentication =
    new UsernamePasswordAuthenticationToken(
        username,
        null,
        List.of(new SimpleGrantedAuthority(role))
    );
authentication.setDetails(Map.of("scope", scope));
SecurityContextHolder.getContext().setAuthentication(authentication);
scope。scope 放進 authentication.setDetails(),讓後續的授權判斷可以讀取。public static String generateAccessToken(String username, String role, String scope) {
    return Jwts.builder()
            .setSubject(username)
            .addClaims(Map.of("role", role, "scope", scope))
            .setIssuedAt(new Date())
            .setExpiration(new Date(System.currentTimeMillis() + 60 * 1000)) // 1 分鐘
            .signWith(key, SignatureAlgorithm.HS256)
            .compact();
}
ROLE_ADMIN + read write。ROLE_USER + read。package com.ansathsean.security;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
@Component("scopeSecurity")
public class ScopeSecurity {
    public boolean hasScope(Authentication authentication, String requiredScope) {
        Object details = authentication.getDetails();
        if (details instanceof java.util.Map<?, ?> map) {
            String scopes = (String) map.get("scope");
            if (scopes != null) {
                for (String s : scopes.split(" ")) {
                    if (s.equals(requiredScope)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }
}
read。@PreAuthorize 中靈活使用。read,不該擁有 write。role:放置在 JWT claims。scope:以空白分隔的字串,方便解析。attribute:如 department,可以來自 DB 或 OIDC Claim。/finance/report。到了這裡,我們完成了 進階授權策略,將 Role + Scope + Attribute 三者結合,實現真正靈活的存取控制。
這樣的設計更貼近真實世界的需求,例如「某部門主管才能審核報表」、「特定 API 僅能在工作時段使用」。這也代表我們將全部的授權與認證的知識都交給大家了!
感謝大家的觀看,不知不覺也已經過了29天,相信大家對於授權與認證的情境了解得更多,我自己在開發的過程中也學到了很多,身為一個工程師,學到最多東西的時段絕對不是開發(畢竟現在有AI在,開發輕鬆很多),最重要的還是在測試的過程中,在試誤的過程中學到更多扎實的內容。希望我學到的這些知識能夠成為大家的養分~
明天就是最後一天,我會整理 Spring Security 實戰 30 天學習總結,寫一些我的心得,非常感謝大家的收看,那我們,明天見!